#pragma once
// 这个头文件是用来实现阻塞队列的，这个是生产者消费者模型中最重要的一环，这就是资源的中转站
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
#include <unistd.h>
#include <pthread.h>
#include <queue>
#include"Mutex.hpp"
#include"Cond.hpp"  //用自己封装的在把这个基于阻塞队列的生产者消费者模型完善一下
#define desize 5   //md宏写错了


using namespace MutexMoble;
using namespace CondMoble;

template <typename T>
class BlockQueue
{
    // 判断阻塞队列是否满了，如果满了就不在往里面放入数据了
    bool Full() { return _q.size() >= _size; }
    // 判断阻塞队列是否为空，如果是空的话那么就不往里面拿数据
    bool Empty() { return _q.empty(); }

public:
    BlockQueue(int size = desize) : _size(desize), _csleep_num(0), _psleep_num(0)
    {
        // 下面是对锁变量和条件变量进行初始化
        // pthread_mutex_init(&_lock, nullptr);
        // pthread_cond_init(&_full_cond, nullptr);
        // pthread_cond_init(&_empty_cond, nullptr);
        

        //此时这里就不需要初始化了，因为我自己封装的在建立的时候就自己进行了初始化操作了
    }

    // 生产者进行往阻塞队列里放入数据
    void Equeue(const T &in)
    {
        // 对临界资源进行访问，先要上锁保证临界资源的安全
        // pthread_mutex_lock(&_lock);  //此时上锁用自己写的函数上锁
        LockGuard s1(_lock);   //直接调用这个组织接口，直接一步到位
        // while循环可以增加代码的健壮性，因为可能在申请锁失败的话，那么可能会出现代码危险的问题
        while (Full())
        {
            // 应该让生产者线程进行等待
            // 重点1：pthread_cond_wait调用成功，挂起当前线程之前，要先自动释放锁！！
            // 重点2：当线程被唤醒的时候，默认就在临界区内唤醒！要从pthread_cond_wait
            // 成功返回，需要当前线程，重新申请_mutex锁！！！
            // 重点3：如果我被唤醒，但是申请锁失败了？？我就会在锁上阻塞等待！！！
            _psleep_num++; // 睡眠的线程增加
            std ::cout << "生产者，进入了休眠：_pthread_num" << _psleep_num << std ::endl;
            _full_cond.Wait(_lock); // 通过调用同步相关的函数让线程进行休眠
            _psleep_num--;                          // 休眠结束，个数减去一个
            // 问题1: pthread_cond_wait是函数吗？有没有可能失败？pthread_cond_wait立即返回了
            // 问题2：pthread_cond_wait可能会因为，条件其实不满足，pthread_cond_wait 伪唤醒
        }

        //此时可以完全确定，线程苏醒了，所以要往里面放入数据了
        _q.push(in);

        //此时里面有了数据，所以可以确保此时的队列不为空，可以把消费者唤醒了
        if(_csleep_num > 0)
        {
            _empty_cond.Signal();  //把相应的线程唤醒
            std::cout << "唤醒消费者.." << std :: endl;
        }
    }


    //开始消费者从里面拿数据
    T Pop()
    {
        //消费者进行调用了，对临街资源访问了，所以需要上锁
        LockGuard s1(_lock);
        while(Empty())
        {
            //此时数据是空的
            _csleep_num++;
            _empty_cond.Wait(_lock);
            //唤醒后，自动减一
            _csleep_num--;
        }
        //此时可以进行数据的拿去了
        T a = _q.front();
        _q.pop();
        //因为出数据了，所以可以让生产者苏醒了
        if(_psleep_num > 0)
        {
            _full_cond.Signal();
            std :: cout << "唤醒生产者.."  << std :: endl;
        }
        return a;
    }

    int Size()
    {
        return _size;
    }
    ~BlockQueue()
    {
        // 对锁和条件变量进行销毁（自己创建的自己销毁）
        //此时封装完以后，它们会自己调用自己的析构函数进行析构处理的
    }

private:
    std :: queue<T> _q;          // 临街资源，也就是阻塞队列（其实就是让一个队列阻塞罢了）
    int _size;                  // 阻塞队列的容量

    Mutex _lock;
    Cond _full_cond;
    Cond _empty_cond;

    //下面的就不需要了，直接用自己封装的就好了
    // pthread_mutex_t _lock;      // 先给这个模型定义好一把锁，从而方便等会进行临界资源的保护
    // pthread_cond_t _full_cond;  // 这个是满了以后，进行线程的休眠（也就是生产者不需要在里面写入数据了）
    // pthread_cond_t _empty_cond; // 这个是如果是空的话，那么消费者无须在里面读取数据了，也是通过同步的方式让线程休眠

    int _csleep_num; // 消费者休眠的个数
    int _psleep_num; // 生产者休眠的个数

};